<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Plugins\Snowflake;

use Srv\Libs\Common\CommTime;
use Swoole\Lock;

class Snowflake
{
    private const EPOCH                 = 1672502400000;                            // 起始时间戳[毫秒][2023-01-01 00:00:00][UTC+8][70年内]
    private const SEQUENCE_BIT          = 12;                                       // 顺序号部分固定12位
    private const SEQUENCE_MAX          = -1 ^ (-1 << self::SEQUENCE_BIT);          // 顺序号最大值[0-4095]
    private const SERVER_BIT            = 9;                                        // 节点部分固定9位
    private const SERVER_MAX            = -1 ^ (-1 << self::SERVER_BIT);            // 节点最大数值[0-511]
    private int $lastTimeMilli          = 0;                                        // 上次时间
    private int $lastSequenceNum        = 0;                                        // 上次序号数
    private ?Lock $SwooleLock           = null;                                     // 互斥锁

    /**
     * __construct
     */
    public function __construct()
    {
        $this->lastTimeMilli            = 0;
        $this->lastSequenceNum          = 0;
        $this->SwooleLock               = new Lock(SWOOLE_MUTEX);
    }

    /**
     * @param int $serverId
     * @return bool
     * isServerId[0-511]
     */
    public function isServerId(int $serverId):bool
    {
        return $serverId >= 0 && $serverId <= self::SERVER_MAX;
    }

    /**
     * @param int $serverId
     * @return int
     * genNewId
     */
    public function genNewId(int $serverId):int
    {
        return $this->genId(max(min($serverId, self::SERVER_MAX), 0));
    }

    /**
     * @return int
     * genSafeId
     */
    public function genSafeId():int
    {
        return $this->genId(-1);
    }

    /**
     * @param int $serverId
     * @return int
     * genId
     */
    private function genId(int $serverId = 0):int
    {
        $serverId                       = max(min($serverId, self::SERVER_MAX), -1);
        $this->SwooleLock->lock();
        $currTimeMilli                  = CommTime::getTimeMilli(0);
        if($this->lastTimeMilli === $currTimeMilli){
            ++$this->lastSequenceNum;
            if($this->lastSequenceNum > self::SEQUENCE_MAX){
                while($currTimeMilli <= $this->lastTimeMilli){
                    $currTimeMilli      = CommTime::getTimeMilli(0);
                }
                $this->lastSequenceNum  = 0;
            }
        }else{
            $this->lastSequenceNum      = 0;
        }
        $this->lastTimeMilli            = $currTimeMilli;
        if($serverId >= 0){
            $id                         = (min($currTimeMilli - self::EPOCH, -1 ^ (-1 << 41)) << self::SEQUENCE_BIT+1+self::SERVER_BIT) | ($serverId << self::SEQUENCE_BIT+1)| (0 << self::SEQUENCE_BIT) | $this->lastSequenceNum;
        }else{
            $id                         = (min($currTimeMilli - self::EPOCH, -1 ^ (-1 << 40)) << self::SEQUENCE_BIT+1) | (1 << self::SEQUENCE_BIT) | $this->lastSequenceNum;
        }
        $this->SwooleLock->unlock();
        return $id;
    }

    /**
     * @param int $id
     * @param bool $isSafe
     * @param int $realTimeMilli
     * @param int $serverId
     * @param int $sequenceId
     * @return bool
     * parseId
     */
    public static function parseId(int $id, bool &$isSafe, int &$realTimeMilli, int &$serverId, int &$sequenceId):bool
    {
        if($id < (-1 ^ (-1 << self::SEQUENCE_BIT+1))) return false;
        if(($id & (1 << self::SEQUENCE_BIT)) === 0){
            $isSafe         = false;
            $realTimeMilli  = ($id >> self::SEQUENCE_BIT+1+self::SERVER_BIT) + self::EPOCH;
            $serverId       = ($id >> self::SEQUENCE_BIT+1) & (-1 ^ (-1 << self::SERVER_BIT));
        }else{
            $isSafe         = true;
            $realTimeMilli  = ($id >> self::SEQUENCE_BIT+1) + self::EPOCH;
            $serverId       = 0;
        }
        $sequenceId         = $id & (-1 ^ (-1 << self::SEQUENCE_BIT));
        return true;
    }

    /**
     * @param int $id
     * @return int
     * getRealTimeMilli
     */
    public static function getRealTimeMilli(int $id):int
    {
        if($id < (-1 ^ (-1 << self::SEQUENCE_BIT+1))) return 0;
        if(($id & (1 << self::SEQUENCE_BIT)) === 0){
            return ($id >> self::SEQUENCE_BIT+1+self::SERVER_BIT) + self::EPOCH;
        }else{
            return ($id >> self::SEQUENCE_BIT+1) + self::EPOCH;
        }
    }

    /**
     * @return int
     * getStartMilliTime
     */
    public static function getStartMilliTime():int
    {
        return self::EPOCH;
    }
}